package com.thiswhyme.ratelimiter.core;

import com.thiswhyme.common.expection.BusinessException;
import com.thiswhyme.ratelimiter.annotation.RateLimit;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @description: 限流切面
 * @author: thiswhyme
 * @date: 2021/8/30
 */
@Slf4j
@Aspect
public class RateLimitAspectHandler implements BeanFactoryAware {

    private final LimitHandler limitHandler;

    private static final ExpressionParser parser = new SpelExpressionParser();

    private BeanFactory beanFactory;

    public RateLimitAspectHandler(LimitHandler limitHandler) {
        this.limitHandler = limitHandler;
    }

    /**
     * 限流处理
     * @param joinPoint
     * @param rateLimit
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
        String subKey = StringUtils.hasText(rateLimit.subKey()) ? getSubKey(joinPoint, rateLimit.subKey()) : "";

        boolean allowed = limitHandler.tryAccess(rateLimit.key(), subKey, getRate(rateLimit),
                rateLimit.rateIntervalMillis(), rateLimit.permits(), rateLimit.waitTimeoutMillis());
        if (!allowed) {
            if (log.isDebugEnabled()) {
                log.debug("资源【{}{}】请求被限流", rateLimit.key(), subKey);
            }
            if (StringUtils.hasText(rateLimit.fallback())) {
                return executeFallback(rateLimit.fallback(), joinPoint);
            }
            throw new BusinessException(RateLimitResponseCode.LIMIT_EXCEPTION.getCode(),
                    RateLimitResponseCode.LIMIT_EXCEPTION.getMsg());
        }
        return joinPoint.proceed();
    }

    /**
     * 获取subKey的真实值
     * @param joinPoint
     * @param subKey
     * @return
     */
    public String getSubKey(ProceedingJoinPoint joinPoint, String subKey) {
        String[] parameterNames = ((MethodSignature)joinPoint.getSignature()).getParameterNames();
        Object[] arguments = joinPoint.getArgs();
        EvaluationContext context = new StandardEvaluationContext();
        for(int i = 0; i < parameterNames.length; ++i) {
            context.setVariable(parameterNames[i], arguments[i]);
        }
        return parser.parseExpression(subKey).getValue(context, String.class);
    }

    /**
     * 获取生效的rate
     * @param rateLimit
     * @return
     */
    private long getRate(RateLimit rateLimit){
        if (StringUtils.hasText(rateLimit.dynamicRate())) {
            String value = ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(rateLimit.dynamicRate());
            if (value != null) {
                return Long.parseLong(value);
            }
        }
        return rateLimit.rate();
    }

    /**
     * 执行自定义降级逻辑
     * @param fallbackMethod
     * @param joinPoint
     * @return
     */
    public Object executeFallback(String fallbackMethod, JoinPoint joinPoint) {
        Method currentMethod = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Object res;
        try {
            Method handleMethod = joinPoint.getTarget().getClass().getDeclaredMethod(fallbackMethod,
                    currentMethod.getParameterTypes());
            handleMethod.setAccessible(true);
            res = handleMethod.invoke(joinPoint.getTarget(), joinPoint.getArgs());
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            log.error("【{}】的降级方法执行失败", currentMethod.getName(), e);
            throw new BusinessException(RateLimitResponseCode.FALLBACK_EXCEPTION.getCode(),
                    RateLimitResponseCode.FALLBACK_EXCEPTION.getMsg());
        }
        return res;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
}
